/*
* 
* 
*/

/////////////////////////////////////////////////////////////////////////

package sheep.model;

/////////////////////////////////////////////////////////////////////////

import edu.uci.ics.jung.graph.Vertex;
import edu.uci.ics.jung.graph.impl.DirectedSparseGraph;
import edu.uci.ics.jung.visualization.DefaultSettableVertexLocationFunction;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.logging.Level;
import java.util.logging.Logger;
import sheep.model.graph.Graph;
import java.util.Calendar;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;

/////////////////////////////////////////////////////////////////////////
import sheep.controller.Workspace;
import sheep.model.graph.CyclicGraphException;
import sheep.model.graph.Graph.Direction;
import sheep.model.graph.OurVertex;

/**
*
* @author geek
*/
public class Task implements Cloneable, Comparable
{

  public Point2D getLocation() {
    return location;
  }

  public void setLocation(Point2D location) {
    if (graph != null && getSuperTask()!=null)
    {
      getSuperTask().graph.setLocation(this, location);
      for(Object o : getSuperTask().graph.getVertices())
      {
        OurVertex v = (OurVertex)o;
        
      }
              
    }
    
    this.location = location;
  }

  
  public Point2D getAutoLocation() {
    return autoLocation;
  }

  public void setAutoLocation(Point2D location) {
    if (graph != null && getSuperTask()!=null)
    {
      getSuperTask().graph.setAutoLocation(this, location);
      for(Object o : getSuperTask().graph.getVertices())
      {
        OurVertex v = (OurVertex)o;
        
      }
              
    }
    
    this.autoLocation = location;
  }
  
  public String getParentID() {
    return parentID;
  }

  public void setParentID(String parentID) {
    this.parentID = parentID;
  }
  /////////////////////////////////////////////////////////////////////////

  /**
  * Defini l'etat actuel de la tache
  */
  public static enum TaskState{
    WAITING,  	// Pas commence
    STARTED,	// Commence
    DONE        // Finie
  }        
  /**
  * Defini la situation actuelle de la tache
  */
  public static enum TaskMode{
    NORMAL,     // Tache simple
    CRITIC      // appartient au chemin crtitic
  }

  public static enum GraphMode{
    NORMAL,
    AUTOARRANGED
  }
  //////////////////////////////////////////////////////////////////////////

  // Identifinant
  protected static int idGeneraor = 0;

  // Nom de la tache
  protected String name;  

  //Tache superieur
  private Task superTask;
  
  // Id de la tache
  protected int iD=0;

  // Commentaire ajouter
  private String comment="";

  private Point2D location = new Point (50,50);
  private Point2D autoLocation = new Point (50,50);
  
  private LinkedList<Worker> managers = new LinkedList<Worker>();
   
  // Duree de la tache
  private int duration = 1;
  
  private String parentID;

  // Date de début et fin  de la tache dans l'état actuel	
  protected Calendar actualBeginTime;	
  private Calendar actualEndTime;        

  // Date theorique du commencement et de la fin de la tache au plus tot	
  protected Calendar earliestBeginTime;	
  private Calendar earliestEndTime;

  // Date theorique du commencement et de la fin de la tache au plus tard	
  protected Calendar latestBeginTime;	
  private Calendar latestEndTime;  

 
  // Précise si la tache est en attente, commencé ou finie
  protected TaskState state= TaskState.WAITING;

  // Precise l'etat de la tache dans le projet        
  protected TaskMode mode = TaskMode.NORMAL;

  private GraphMode graphMode = GraphMode.AUTOARRANGED;
  
  // Defini si il existe un graphe de sous taches   
  protected boolean detailled = false;        
  
  // Defines whether the task is critical and is part of the critical path
  private boolean critical = false;
  
  // sous-graphe de la tache
  protected Graph graph;

  private Graph normalGraph;
  
  private LinkedList<Worker> workers = new LinkedList<Worker>();
   
  private Workspace workSpace;
  
  // Pour simplifier les traitements listes de premieres et dernieres taches
  // de la liste de sous tache
  //protected LinkedList<Task> beginTaskList, endTaskLList;   

  //////////////////////////////////////////////////////////////////////////

  // Constructeurs tache simple
  public Task (String name, Workspace workSpace){
    this.name = name;
    this.workSpace = workSpace;
    actualBeginTime = Calendar.getInstance();
    actualEndTime = Calendar.getInstance();
    earliestBeginTime = Calendar.getInstance();
    earliestEndTime = Calendar.getInstance();
    latestBeginTime = Calendar.getInstance();
    latestEndTime = Calendar.getInstance();
    //Ajout d'un Identifiant "unique"
    iD = idGeneraor++;
  }

  public Task(String name, int duration, Workspace workSpace){
    this(name, workSpace);
    this.duration = duration;
  }
  
  
  // Constructeur tache avec sous graphe
  public Task (String name, int duration, boolean isDetailled, Workspace workSpace){
    this(name, duration, workSpace);
    // Information pour le sous graphe
    this.detailled = isDetailled;
    graph = new Graph(workSpace);
  }

  public LinkedList<Task> getTaskList(boolean detailled, boolean fromStart){
    LinkedList<Task> list = new LinkedList<Task>();
    Task temp = this, temp2;
    if (fromStart)
      while (temp.getSuperTask()!= null){
        temp = temp.getSuperTask();
      }
      list.add(temp);
      if (temp.graph!=null)
        for (Object v : temp.graph.getVertices()){
          temp2 = temp.graph.getTask((OurVertex)v);
          if (!temp2.getName().equals("start")&&!temp2.getName().equals("end")){
            list.add (temp2);
          }
          if (detailled)
            if (temp2.isDetailled()){
              list.addAll(temp2.getTaskList(false, false));
              if (temp2.getTaskList(false, false).size()>0)
                list.remove(temp2);
            }
        }
      return list;
  }
  
  public Object[][] getTaskTab(boolean detailled, boolean fromStart){
    LinkedList<Task> list = getTaskList(detailled, fromStart);
    Object[][] result = new Object [list.size()][4];
    int i=0;
    for(Task w : list){     
        result[i][0] = w.getName();
        result[i][1] = w.getActualBeginTime().getTime().toString().substring(0, 13);
        result[i][2] = w.getActualEndTime().getTime().toString().substring(0, 13);
        result[i][3] = "";
        i++;
    }
    
    return result;    
    
  }
  public Object[][] getWorkersTab() {
    Object[][] result = new Object [workers.size()][Worker.workerTabLength];
    
    int i=0;
    for(Worker w : workers){
      result[i] = w.getWorkerTab();
      i++;
    }
    
    return result;
  }
  
  public Worker getWorker(Object id) {
    Worker result = null;
    
    for(Worker w : workers) {
      if(w.getId() == id )
        result = w;
    }
    
    return result;
  }
  
  public LinkedList<Worker> getManagers() {
    return managers;
  }

  public void setManagers(LinkedList<Worker> managers) {
    this.managers = managers;
  }

    public Object[][] getManagersTab() {
    Object[][] result = new Object [managers.size()][Worker.workerTabLength];
    
    int i=0;
    for(Worker w : managers) {
      result[i] = w.getWorkerTab();
      i++;
    }
    return result;
  }
  
  public Worker getManager(Object id) {
    Worker result = null;
    
    for(Worker w : managers) {
      if(w.getId() == id)
        result = w;
    }
    return result;
  }
  
  
  //////////////////////////////////////////////////////////////////////////

  // Getters et Setters du nom
  public String getName(){ 
    return name;
  }
  
  public void   setName(String name){
    this.name = name;
  }
  
  
  public Task getSuperTask() {
    return superTask;
  }

  public void setSuperTask(Task superTask) {
    this.superTask = superTask;
    if(this.getSuperTask() != null)
      this.setActualBeginTime(this.getSuperTask().getActualBeginTime(), true);
//    if (superTask != null)
//      this.earliestBeginTime.setTimeInMillis(superTask.getEarliestBeginTime().getTimeInMillis());
  }
  
  //////////////////////////////////////////////////////////////////////////

  //Getters de l'ID
  public int getID(){
    return iD;
  }
  
  public void setID(int id) { this.iD = id; }

  //////////////////////////////////////////////////////////////////////////

  // Getters et Setters de l'etat
  public TaskState getState(){
    return state;
  }
  
  
  // Essaie d'affecter un mode mais verifie la coherence avec le sous graphe
  public void setState(TaskState state){
    this.state = state;
    if (isDetailled())
    validState();
  }  
  
  
  // Impose l'etat a une tache et ses sous taches
  public void initState(TaskState state){
    this.state = state;
    if (isDetailled())
      for(Object o : this.getGraph().getVertices())
        this.getGraph().getTask((OurVertex)o).setState(state);
    //            for (Task t: taskList)
    //             t.setState(state); 
  }
  
  // assure la coherence entre l'état de la tache et le sous graphe
  public void validState(){
    boolean done = true; 
    // on verifie la coherence avec le sous graphe
    // si il existe sinon on ne fait rien
    if (isDetailled() && this.getGraph() != null)
    {
      // si il n'a pas de sous taches on change rien
      if (getGraph().numVertices() != 0)
      {
        // Pour toute les taches
        for (Object o : this.getGraph().getVertices())
        {
          Task t = this.getGraph().getTask((OurVertex)o);
          // Si au moins 1 est commencee
          if(t.state == TaskState.STARTED)
          {
            // On est en mode en cours
            state = TaskState.STARTED;
            //on quitte
            return;
            // Sinon on check qu'elles sont toutes faites
          }
          else if(t.state == TaskState.WAITING)
            done = false;
        }
      }
    }
    // On prend l'etat en fonction du flag
    state = done ? TaskState.DONE : TaskState.WAITING ;
  }

  //////////////////////////////////////////////////////////////////////////

  // Getters et Setters du mode
  public TaskMode getMode(){
    return mode;
  }
  
  
  // Impose la mode a une tache
  public void setMode(TaskMode mode){
    this.mode = mode;
  }

  public GraphMode getGraphMode(){
    return graphMode;
  }
  
  public void setGraphMode(GraphMode graphMode){
    this.graphMode = graphMode;
  }

  public boolean isDetailled(){
    return detailled;
  }

  public void setDetailled(boolean detailled){
    this.detailled = detailled;
  }
  
  public boolean isCritical(){
    return critical;
  }
  
  public void setCritical(boolean critical){
    this.critical = critical;
  }
  
  public int getDuration() {
    return duration;
  }

  public void setDuration(int duration, boolean withUpdate) {
    this.duration = duration;
    if(withUpdate){
      Calendar calTemp = Calendar.getInstance();
      
      calTemp.setTime(this.actualBeginTime.getTime());
      calTemp.add(Calendar.DATE, duration-1);
      this.setActualEndTime(calTemp, false);
      
      calTemp.setTime(this.earliestBeginTime.getTime());
      calTemp.add(Calendar.DATE, duration-1);
      this.setEarliestEndTime(calTemp);
      
      calTemp.setTime(this.latestBeginTime.getTime());
      calTemp.add(Calendar.DATE, duration-1);
      this.setLatestEndTime(calTemp);
      
      //if(this.workSpace.getCurrentProject().isDetailled())
        this.workSpace.getCurrentProject().getGraph().criticalPath(true);
    }
    
//    if(this.graph != null){
//      Task taskTemp = this.graph.parse();
//      Graph graph = taskTemp.getGraph();
      
//      if(graph != null){
//        try {
//       graph.criticalPath(true);
//        } 
//        catch (CyclicGraphException ex) {}
//      }
//    }
  }  
  
//  public void updateProject(){
//    this.setEarliestBeginTime(this.actualBeginTime);
//    this.getGraph().getStartTask().setActualBeginTime(this.actualBeginTime, false);
//    
//    //try {
//      this.getGraph().criticalPath(true);//, Direction.BOTH);
//    //} catch (CyclicGraphException ex) {}
//    
//    this.setActualEndTime(this.getGraph().getEndTask().getActualEndTime(), true);
//  }
  
  //////////////////////////////////////////////////////////////////////////

  // Getters et Setters du depart effectif
  public Calendar getActualBeginTime()
  { 
    return actualBeginTime; 
  }

  // Mise a jour de time de depart et mise a jour du sous graphe si necessaire
  public void  setActualBeginTime(Calendar beginTime, boolean withUpdate)
  {
    // On affecte la tache
    this.actualBeginTime = (Calendar)beginTime.clone();
    
//    // On evite les inciherence avec les sous taches
//    if (isDetailled() && this.getGraph() != null){
//      this.getGraph().getStartTask().setActualBeginTime(beginTime, false);
//      for(Object v : this.getGraph().getVertices())
//        ((OurVertex)v).getTask().setActualBeginTime(beginTime, false);
        
   // }
    if(withUpdate){
      Calendar cal = Calendar.getInstance();
      cal.setTime(actualBeginTime.getTime());
      cal.add(Calendar.DATE, this.getDuration() - 1);
      actualEndTime.setTime(cal.getTime());
//      if(this.workSpace != null)
//        this.workSpace.getCurrentProject().getGraph().criticalPath(true);
    }
    
    
    
//    if(withUpdate)
//      if(!this.getName().equals("start") && !this.getName().equals("end")){
//        Calendar cal = Calendar.getInstance();
//        cal.setTime(this.getActualBeginTime().getTime());
//        cal.add(Calendar.DATE, this.duration - 1);
//        this.setActualEndTime(cal, false);
////        this.duration = (int)((actualEndTime.getTimeInMillis() - actualBeginTime.getTimeInMillis()) / 
////              (1000*60*60*24)) + 1;
//      }
    
    
    
  }
  
  public void updateMotherDuration(){
    this.superTask.setDuration((int)(this.superTask.getGraph().getEndTask().getActualEndTime().getTimeInMillis() - 
            this.superTask.getGraph().getStartTask().getActualBeginTime().getTimeInMillis())/(1000*3600*24) + 1, true);
  }
  
  // Calcul l'actual time en fonction des sous taches
  public boolean  updateActualBeginTime (){

    //Calendar beginTime = this.isDetailled() && this.graph.numVertices() > 0 ?

    //Calendar beginTime = taskList != null&& taskList.size()>0? taskList.getFirst().actualBeginTime: null;
    // On evite les inciherence avec les sous taches
    if(isDetailled() && this.getGraph() != null)
    {
      //Calendar temp = Calendar.getInstance();
      for(Object v : this.getGraph().getVertices())
      {
        if(this.actualBeginTime == null || this.actualBeginTime.after(
                this.getGraph().getTask((OurVertex)v).actualBeginTime))
          this.actualBeginTime = ((OurVertex)v).getTask().actualBeginTime;
      }
    }
    return false;
  }

  //////////////////////////////////////////////////////////////////////////

  // Getters et Setters du depart le plus tot theorique
  public Calendar getEarliestBeginTime(){
    return earliestBeginTime;
  }
  
  public void setEarliestBeginTime(Calendar beginTime, boolean withUpdate){
    this.earliestBeginTime = (Calendar)beginTime.clone();
    if(withUpdate){
      Calendar cal = Calendar.getInstance();
      cal.setTime(earliestBeginTime.getTime());
      cal.add(Calendar.DATE, this.getDuration() - 1);
      earliestEndTime.setTime(cal.getTime());
    }
    //if(this.isDetailled() && this.getGraph() != null)
      //this.getGraph().getStartTask().setEarliestBeginTime(beginTime);
  }    

  //////////////////////////////////////////////////////////////////////////

  // Getters et Setters du depart le plus tard theorique
  public Calendar getLatestBeginTime(){
    return latestBeginTime;
  }
  
  
  public void setLatestBeginTime(Calendar beginTime, boolean withUpdate){
    this.latestBeginTime = (Calendar)beginTime.clone();
//    if(this.isDetailled() && this.getGraph() != null)
//      this.getGraph().getStartTask().setLatestBeginTime(beginTime);
    if(withUpdate){
      Calendar cal = Calendar.getInstance();
      cal.setTime(latestBeginTime.getTime());
      cal.add(Calendar.DATE, this.getDuration() - 1);
      latestEndTime.setTime(cal.getTime());
    }
  }
  
  public Calendar getActualEndTime() {
    return actualEndTime;
  }

  public void setActualEndTime(Calendar endTime, boolean withUpdate) {
    this.actualEndTime = (Calendar)endTime.clone();
    
//    // On evite les incoherence avec les sous taches
//    if (isDetailled() && this.getGraph() != null){
//      this.getGraph().getEndTask().setActualBeginTime(endTime, false);
//      this.getGraph().getEndTask().setActualEndTime(endTime, false);
      
//    }
    if(withUpdate)
      if(!this.getName().equals("start") && !this.getName().equals("end")){
        this.duration = (int)((actualEndTime.getTimeInMillis() - actualBeginTime.getTimeInMillis()) / 
              (1000*60*60*24)) + 1;
        //this.workSpace.getCurrentProject().getGraph().criticalPath(true);
    }
  }

  public Calendar getEarliestEndTime() {
    return earliestEndTime;
  }

  public void setEarliestEndTime(Calendar earliestEndTime) {
    this.earliestEndTime = (Calendar)earliestEndTime.clone();
  }

  public Calendar getLatestEndTime() {
    return latestEndTime;
  }

  public void setLatestEndTime(Calendar latestEndTime) {
    this.latestEndTime = (Calendar)latestEndTime.clone();
  }

  public String getComment() {
    return comment;
  }

  public void setComment(String comment) {
    this.comment = comment;
  }
  
  public Graph getGraph(){
      return graph;

  }

  public void setGraph(Graph graph) {
    this.graph = graph;
  }
  
//  public Graph getNormalGraph(){
//    return normalGraph;
//  }
//  
//  public void setNormalGraph(Graph normalGraph){
//    this.normalGraph = (Graph)normalGraph.copy();
//  }
  
  
  public void addGraph(Task superTask) {
    this.graph = new Graph(workSpace);

   
    this.getGraph().getStartTask().setSuperTask(this);
    this.getGraph().getEndTask().setSuperTask(this);
    

    
    this.setSuperTask(superTask);
    this.detailled = false;
    this.graph.setName(name);

  }  
  
  public void autoArranged(){
    LinkedList<LinkedList<OurVertex>> column = graph.deepSort();
    int i = 1;
    int j = 1;
//    for(LinkedList<OurVertex> list : column){
//      for(OurVertex v : list){
//        System.out.println(i +"." + j + " : " + v.getTask().getName());
//        j++;
//      }
//      j = 1;
//      i++; 
//    }
    System.out.println("graph width : " + graph.getAutoVv().inverseTransform(
            new Point(graph.getAutoVv().getSize().width,graph.getAutoVv().getSize().height)).getX());//graph.getAutoLayout().getCurrentSize().getSize().width);
    System.out.println("nb colonne : " + column.size());
    int columnSize = (int)((graph.getAutoVv().inverseTransform(
            new Point(graph.getAutoVv().getSize().width,graph.getAutoVv().getSize().height)).getX()-250) / (column.size()-1));
    //.getAutoLayout().getCurrentSize().getSize().width) / column.size() + 100;
    System.out.println("colonne size : " + columnSize);
    int lineSize;
    int widthPos = 50;
    int heightPos = 0;
    
    for(LinkedList<OurVertex> vertices : column){
      lineSize = (int)((graph.getAutoVv().inverseTransform(
            new Point(graph.getAutoVv().getSize().width,graph.getAutoVv().getSize().height)).getY()) / (vertices.size()+1));
      for(Object v : vertices){
        heightPos = heightPos + lineSize;
        System.out.println("auto arrange :" + ((OurVertex)v).getTask().getName() +
                "  X : " + widthPos + ", Y : " + heightPos);
        ((OurVertex)v).getTask().setAutoLocation(new Point(widthPos, heightPos));
        ((OurVertex)v).getTask().getSuperTask().getGraph().getAutoVertexLocations().setLocation((OurVertex)v, new Point(widthPos,heightPos));
      }
      widthPos = widthPos + columnSize;
      heightPos = 0;
    }
//    for(Object t : this.getGraph().getVertices()){
//      Task task = ((OurVertex)t).getTask();
//      System.out.println(task.getName() + "  X : " + task.getLocation().getX() + ", Y : " + task.getLocation().getY());
//    }
    
  }
  
  
  

  @Override
  public String toString(){
    String ret = new String();
    //String tab = new String("");
    //if(superTask != null)
      //tab = "  ";
    ret = name + " : ";
    if(this.getEarliestBeginTime() != null)
      ret += " erliest begin : " + this.getEarliestBeginTime().get(Calendar.DATE)
              + "." + (this.getEarliestBeginTime().get(Calendar.MONTH)+1) + "."
              + this.getEarliestBeginTime().get(Calendar.YEAR)+ "\n"
              + "     latest begin : " + this.getLatestBeginTime().get(Calendar.DATE)
              + "." + (this.getLatestBeginTime().get(Calendar.MONTH)+1) + "."
              + this.getLatestBeginTime().get(Calendar.YEAR)+ "\n"
              + "     critique : " + (this.isCritical() ? "oui":"non") + "\n" 
              + "     duree : " + this.duration;
    
    return ret;
  }

  
  public LinkedList<Worker> getWorkersList() {
    return workers;
  }
  
  public void setWorkersList(LinkedList<Worker> list) { this.workers = list; }

  public void setWorkers(LinkedList<Worker> workers) {
    this.workers = workers;
  }

  @Override
  public int compareTo(Object comp) {
    Task t = (Task) comp;
    
    if(t.getEarliestBeginTime().get(Calendar.YEAR) <= this.getEarliestBeginTime().get(Calendar.YEAR) &&
       t.getEarliestBeginTime().get(Calendar.DAY_OF_YEAR) <= this.getEarliestBeginTime().get(Calendar.DAY_OF_YEAR) &&
       (t.getDuration() < this.getDuration()|| t.getEarliestBeginTime().get(Calendar.DAY_OF_YEAR) < this.getEarliestBeginTime().get(Calendar.DAY_OF_YEAR)))
      return 1;
    
    else if(t.getEarliestBeginTime().get(Calendar.YEAR) >=this.getEarliestBeginTime().get(Calendar.YEAR) &&
       t.getEarliestBeginTime().get(Calendar.DAY_OF_YEAR) >= this.getEarliestBeginTime().get(Calendar.DAY_OF_YEAR) &&
       (t.getDuration() > this.getDuration()|| t.getEarliestBeginTime().get(Calendar.DAY_OF_YEAR) > this.getEarliestBeginTime().get(Calendar.DAY_OF_YEAR)))
      return -1;
    
    else
      return 0;
    
  }
}
